/**
 * Geo Location object. Uses best available means to get accurate fix on client.
 * Parts of this taken from: http://ajaxian.com/archives/navigatorgeolocation-using-the-w3c-geolocation-api-today to use any available geolocation method.
 * 
 * .method              : One of: HTML5, GOOGLE_GEARS, GOOGLE_AJAX_API.
 * .ajaxReason          : One of: NO_HTML5_OR_GEARS, GEARS_DENIED.
 */
function GeoLocation()
{
	// Define location methods.
    GeoLocation.methods = {HTML5: 0, GOOGLE_GEARS: 1, GOOGLE_AJAX_API: 2};
    
    // Define method reasons.
    GeoLocation.ajaxReasons = {NO_HTML5_OR_GEARS: 0, GEARS_DENIED: 1}; 
    
    // Possible errors.
    GeoLocation.errorCodes = {UNKNOWN_ERROR: 0, PERMISSION_DENIED: 1, POSITION_UNAVAILABLE: 2, TIMEOUT: 3};
    
    // Detect platform.
    GeoLocation.detectMethod();
}

/**
 * Get current position.
 */
GeoLocation.getCurrentPosition = function(successCallback, errorCallback)
{
	var options = {enableHighAccuracy:true};
	switch (GeoLocation.method)
    {
        case GeoLocation.methods.HTML5:
            navigator.geolocation.getCurrentPosition(successCallback, GeoLocation.wrapErrorCallback(successCallback, errorCallback), options);
            break;
        case GeoLocation.methods.GOOGLE_GEARS:
            GeoLocation.gears.getCurrentPosition(GeoLocation.wrapSuccessCallback(successCallback), GeoLocation.wrapErrorCallback(successCallback, errorCallback),
                options);
            break;
        case GeoLocation.methods.GOOGLE_AJAX_API:
            GeoLocation.useGoogleAjaxApi(successCallback, errorCallback);
            break;
    }
};

/**
 * Get position using Google AJAX API.
 */
GeoLocation.useGoogleAjaxApi = function(successCallback, errorCallback, usedAsFallback)
{
	if (google.loader.ClientLocation)
    {
        var cl = google.loader.ClientLocation;
        var position =
        {
            coords:
            {
                latitude: cl.latitude,
                longitude: cl.longitude,
                accuracy: 43000 // same as Gears accuracy over wifi?
            },
            timestamp: new Date(),
            ajaxApiUsedAsFallback: usedAsFallback == true ? true : false
        };
        successCallback(position);
    }
    else if (errorCallback === "function")
    {
        errorCallback({code: GeoLocation.errorCodes.POSITION_UNAVAILABLE});
    }
}

/**
 * Watch position.
 */
GeoLocation.watch = function(successCallback, errorCallback)
{
	var options = {enableHighAccuracy:true};
    switch (GeoLocation.method)
    {
        case GeoLocation.methods.HTML5:
            navigator.geolocation.watchPosition(successCallback, errorCallback, options);
            break;
        case GeoLocation.methods.GOOGLE_GEARS:
            GeoLocation.gears.watchPosition(GeoLocation.wrapSuccessCallback(successCallback), errorCallback, options);
            break;
        case GeoLocation.methods.GOOGLE_AJAX_API:
            if (errorCallback === "function")
            {
                errorCallback({code: GeoLocation.errorCodes.POSITION_UNAVAILABLE});
            }
            break;
    }
};

/**
 * Clear watch.
 */
GeoLocation.clearWatch = function(watchId)
{
    switch (GeoLocation.method)
    {
        case GeoLocation.methods.HTML5:
            navigator.geolocation.clearWatch(watchId);
            break;
        case GeoLocation.methods.GOOGLE_GEARS:
            GeoLocation.gears.clearWatch(watchId);
            break;
    }
};

/**
 * Detects available geolocation method.
 */
GeoLocation.detectMethod = function()
{
	if (typeof navigator.geolocation == "undefined")
	{
		// Try Gears.
		if (!GeoLocation.detectGoogleGears())
		{
			// Use AJAX API.
			GeoLocation.method = GeoLocation.methods.GOOGLE_AJAX_API;
			GeoLocation.ajaxReason = GeoLocation.ajaxReasons.NO_HTML5_OR_GEARS;
		}
		else
		{
			// Use Gears?
			GeoLocation.gears = google.gears.factory.create('beta.geolocation');
			if (GeoLocation.gears.hasPermission)
			{
				// Gears is allowed.
			    GeoLocation.method = GeoLocation.methods.GOOGLE_GEARS;
			}
			else
			{
				// Gears is not allowed. Use AJAX API.
                GeoLocation.method = GeoLocation.methods.GOOGLE_AJAX_API;
                GeoLocation.ajaxReason = GeoLocation.ajaxReasons.GEARS_DENIED;
			}
		}
	}
	else
	{
		// Already defined. Must be because of HTML5 browser.
		GeoLocation.method = GeoLocation.methods.HTML5;
	}
};

/**
 * Detects if Google Gears can be used.
 * 
 * @return true if Gears can be used.
 */
GeoLocation.detectGoogleGears = function()
{
	// Load.
    GeoLocation.loadGears();
    return (window.google && google.gears)
};

/**
 * Wraps a successful Gears callback, in order to return results in correct format.
 * 
 * @param successCallback The function to call on successful callback.
 * 
 * @return The wrapped callback function.
 */
GeoLocation.wrapSuccessCallback = function(successCallback)
{
    return function(position)
    {
        // Convert to HTML5 version.
        var html5Position =
        {
            coords:
            {
                latitude: position.latitude,
                longitude: position.longitude,
                accuracy: position.accuracy
            },
            timestamp: position.timestamp
        };
        successCallback(html5Position);
    };
};

/**
 * Wraps an error HTML5 or Gears callback, in order to attempt Google AJAX API lookup as last resort.
 * 
 * @param successCallback The function to call on successful callback.
 * @param errorCallback The function to call on error callback.
 * 
 * @return The wrapped callback function.
 */
GeoLocation.wrapErrorCallback = function(successCallback, errorCallback)
{
    return function()
    {
        GeoLocation.useGoogleAjaxApi(successCallback, errorCallback, true); // Used as fallback.
    };
};

GeoLocation.loadGears =
// Copyright 2007, Google Inc.
//
// Redistribution and use in source and binary forms, with or without
// modification, are permitted provided that the following conditions are met:
//
//  1. Redistributions of source code must retain the above copyright notice,
//     this list of conditions and the following disclaimer.
//  2. Redistributions in binary form must reproduce the above copyright notice,
//     this list of conditions and the following disclaimer in the documentation
//     and/or other materials provided with the distribution.
//  3. Neither the name of Google Inc. nor the names of its contributors may be
//     used to endorse or promote products derived from this software without
//     specific prior written permission.
//
// THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR IMPLIED
// WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
// MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO
// EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
// PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS;
// OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
// WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR
// OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF
// ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
//
// Sets up google.gears.*, which is *the only* supported way to access Gears.
//
// Circumvent this file at your own risk!
//
// In the future, Gears may automatically define google.gears.* without this
// file. Gears may use these objects to transparently fix bugs and compatibility
// issues. Applications that use the code below will continue to work seamlessly
// when that happens.
function() {
  // We are already defined. Hooray!
  if (window.google && google.gears) {
    return;
  }

  var factory = null;

  // Firefox
  if (typeof GearsFactory != 'undefined') {
    factory = new GearsFactory();
  } else {
    // IE
    try {
      factory = new ActiveXObject('Gears.Factory');
      // privateSetGlobalObject is only required and supported on IE Mobile on
      // WinCE.
      if (factory.getBuildInfo().indexOf('ie_mobile') != -1) {
        factory.privateSetGlobalObject(this);
      }
    } catch (e) {
      // Safari
      if ((typeof navigator.mimeTypes != 'undefined')
           && navigator.mimeTypes["application/x-googlegears"]) {
        factory = document.createElement("object");
        factory.style.display = "none";
        factory.width = 0;
        factory.height = 0;
        factory.type = "application/x-googlegears";
        document.documentElement.appendChild(factory);
      }
    }
  }

  // *Do not* define any objects if Gears is not installed. This mimics the
  // behavior of Gears defining the objects in the future.
  if (!factory) {
    return;
  }

  // Now set up the objects, being careful not to overwrite anything.
  //
  // Note: In Internet Explorer for Windows Mobile, you can't add properties to
  // the window object. However, global objects are automatically added as
  // properties of the window object in all browsers.
  if (!window.google) {
    google = {};
  }

  if (!google.gears) {
    google.gears = {factory: factory};
  }
};